Skip to content

Commit 952fe05

Browse files
committed
Providing explicit support for Groovy source code (fixes #13)
Supporting dedicated configuration for groovy source. The configuration allows access to formatter steps which have been introduced for Java, but which are also useable for Groovy code. Integrates Groovy-Eclipse based Groovy formatter step implementation. Allows coexistent configuration of Java and Groovy formatter steps.
1 parent 26fd7cf commit 952fe05

File tree

16 files changed

+575
-10
lines changed

16 files changed

+575
-10
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ lib('generic.LicenseHeaderStep') +'{{yes}} | {{no}}
3838
lib('generic.ReplaceRegexStep') +'{{yes}} | {{no}} | {{no}} |',
3939
lib('generic.ReplaceStep') +'{{yes}} | {{no}} | {{no}} |',
4040
lib('generic.TrimTrailingWhitespaceStep') +'{{yes}} | {{no}} | {{no}} |',
41+
extra('groovy.GrEclipseFormatterStep') +'{{yes}} | {{no}} | {{no}} |',
4142
lib('java.GoogleJavaFormatStep') +'{{yes}} | {{no}} | {{no}} |',
4243
lib('java.ImportOrderStep') +'{{yes}} | {{no}} | {{no}} |',
4344
extra('java.EclipseFormatterStep') +'{{yes}} | {{no}} | {{no}} |',
@@ -58,6 +59,7 @@ lib('scala.ScalaFmtStep') +'{{yes}} | {{no}}
5859
| [`generic.ReplaceRegexStep`](lib/src/main/java/com/diffplug/spotless/generic/ReplaceRegexStep.java) | :+1: | :white_large_square: | :white_large_square: |
5960
| [`generic.ReplaceStep`](lib/src/main/java/com/diffplug/spotless/generic/ReplaceStep.java) | :+1: | :white_large_square: | :white_large_square: |
6061
| [`generic.TrimTrailingWhitespaceStep`](lib/src/main/java/com/diffplug/spotless/generic/TrimTrailingWhitespaceStep.java) | :+1: | :white_large_square: | :white_large_square: |
62+
| [`groovy.GrEclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java) | :+1: | :white_large_square: | :white_large_square: |
6163
| [`java.GoogleJavaFormatStep`](lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java) | :+1: | :white_large_square: | :white_large_square: |
6264
| [`java.ImportOrderStep`](lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java) | :+1: | :white_large_square: | :white_large_square: |
6365
| [`java.EclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseFormatterStep.java) | :+1: | :white_large_square: | :white_large_square: |
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2016 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.extra.groovy;
17+
18+
import java.io.File;
19+
import java.io.Serializable;
20+
import java.lang.reflect.InvocationTargetException;
21+
import java.lang.reflect.Method;
22+
import java.util.Objects;
23+
import java.util.Properties;
24+
25+
import com.diffplug.spotless.FileSignature;
26+
import com.diffplug.spotless.FormatterFunc;
27+
import com.diffplug.spotless.FormatterProperties;
28+
import com.diffplug.spotless.FormatterStep;
29+
import com.diffplug.spotless.JarState;
30+
import com.diffplug.spotless.Provisioner;
31+
32+
/** Formatter step which calls out to the Groovy-Eclipse formatter. */
33+
public class GrEclipseFormatterStep {
34+
public static final String defaultVersion() {
35+
return DEFAULT_VERSION;
36+
}
37+
38+
private static final String NAME = "groovy-eclipse formatter";
39+
private static final String FORMATTER_CLASS = "com.diffplug.gradle.spotless.groovy.eclipse.GrEclipseFormatterStepImpl";
40+
private static final String DEFAULT_VERSION = "2.3.0-SNAPSHOT";
41+
private static final String MAVEN_COORDINATE = "com.diffplug.spotless:spotless-ext-greclipse:";
42+
private static final String FORMATTER_METHOD = "format";
43+
44+
/** Creates a formatter step using the default version for the given settings file. */
45+
public static FormatterStep create(Iterable<File> settingsFiles, Provisioner provisioner) {
46+
return create(defaultVersion(), settingsFiles, provisioner);
47+
}
48+
49+
/** Creates a formatter step for the given version and settings file. */
50+
public static FormatterStep create(String version, Iterable<File> settingsFiles, Provisioner provisioner) {
51+
return FormatterStep.createLazy(
52+
NAME,
53+
() -> new State(JarState.from(MAVEN_COORDINATE + version, provisioner), settingsFiles),
54+
State::createFormat);
55+
}
56+
57+
private static class State implements Serializable {
58+
private static final long serialVersionUID = 1L;
59+
60+
/** The jar that contains the eclipse formatter. */
61+
final JarState jarState;
62+
/** The signature of the settings file. */
63+
final FileSignature settings;
64+
65+
State(JarState jar, final Iterable<File> settingsFiles) throws Exception {
66+
this.jarState = Objects.requireNonNull(jar);
67+
this.settings = FileSignature.signAsList(settingsFiles);
68+
}
69+
70+
FormatterFunc createFormat() throws Exception {
71+
FormatterProperties preferences = FormatterProperties.from(settings.files());
72+
73+
ClassLoader classLoader = jarState.getClassLoader();
74+
75+
// instantiate the formatter and get its format method
76+
Class<?> formatterClazz = classLoader.loadClass(FORMATTER_CLASS);
77+
Object formatter = formatterClazz.getConstructor(Properties.class).newInstance(preferences.getProperties());
78+
Method method = formatterClazz.getMethod(FORMATTER_METHOD, String.class);
79+
return input -> {
80+
try {
81+
return (String) method.invoke(formatter, input);
82+
} catch (InvocationTargetException exceptionWrapper) {
83+
Throwable throwable = exceptionWrapper.getTargetException();
84+
Exception exception = (throwable instanceof Exception) ? (Exception) throwable : null;
85+
throw (null == exception) ? exceptionWrapper : exception;
86+
}
87+
};
88+
}
89+
}
90+
91+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@ParametersAreNonnullByDefault
2+
@ReturnValuesAreNonnullByDefault
3+
package com.diffplug.spotless.extra.groovy;
4+
5+
import javax.annotation.ParametersAreNonnullByDefault;
6+
7+
import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2016 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.extra.groovy;
17+
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.util.List;
21+
22+
import org.junit.Test;
23+
24+
import com.diffplug.spotless.FormatterStep;
25+
import com.diffplug.spotless.ResourceHarness;
26+
import com.diffplug.spotless.SerializableEqualityTester;
27+
import com.diffplug.spotless.StepHarness;
28+
import com.diffplug.spotless.TestProvisioner;
29+
30+
public class GrEclipseFormatterStepTest extends ResourceHarness {
31+
32+
private static final String RESOURCE_PATH = "groovy/greclipse/format/";
33+
private static final String CONFIG_FILE = RESOURCE_PATH + "greclipse.properties";
34+
35+
//String is hard-coded in the GrEclipseFormatter
36+
private static final String FORMATTER_FILENAME_REPALCEMENT = "Hello.groovy";
37+
38+
@Test
39+
public void nominal() throws Throwable {
40+
List<File> config = createTestFiles(CONFIG_FILE);
41+
StepHarness.forStep(GrEclipseFormatterStep.create(config, TestProvisioner.snapshots()))
42+
.testResource(RESOURCE_PATH + "unformatted.test", RESOURCE_PATH + "formatted.test");
43+
}
44+
45+
@Test
46+
public void formatterException() throws Throwable {
47+
List<File> config = createTestFiles(CONFIG_FILE);
48+
StepHarness.forStep(GrEclipseFormatterStep.create(config, TestProvisioner.snapshots()))
49+
.testException(RESOURCE_PATH + "exception.test", assertion -> {
50+
assertion.isInstanceOf(IllegalArgumentException.class);
51+
assertion.hasMessageContaining(FORMATTER_FILENAME_REPALCEMENT);
52+
});
53+
}
54+
55+
@Test
56+
public void configurationException() throws Throwable {
57+
String configFileName = "greclipse.exception";
58+
List<File> config = createTestFiles(RESOURCE_PATH + configFileName);
59+
StepHarness.forStep(GrEclipseFormatterStep.create(config, TestProvisioner.snapshots()))
60+
.testException(RESOURCE_PATH + "unformatted.test", assertion -> {
61+
assertion.isInstanceOf(IllegalArgumentException.class);
62+
assertion.hasMessageContaining(configFileName);
63+
});
64+
}
65+
66+
@Test
67+
public void equality() throws IOException {
68+
List<File> configFile = createTestFiles(CONFIG_FILE);
69+
new SerializableEqualityTester() {
70+
71+
@Override
72+
protected void setupTest(API api) {
73+
api.areDifferentThan();
74+
}
75+
76+
@Override
77+
protected FormatterStep create() {
78+
return GrEclipseFormatterStep.create(configFile, TestProvisioner.snapshots());
79+
}
80+
}.testEquals();
81+
}
82+
83+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Class description
3+
*/
4+
class Foo{
5+
String foo
6+
7+
/* Method */
8+
def callBar() {
9+
new Bar().bar(foo)
10+
}
11+
//Compiler error. Missing close-brace.
12+
//
13+
//Note that the error message is additionally logged to system-err
14+
//within GrEclipse itself. That error message is just a dump of a RAW data report.
15+
//The whole error handling therefore looks not nicely readable.
16+
//Since these errors should only occur if the input file is not compile-able,
17+
//the error format is considered acceptable, since the spotless task runs
18+
//in general only after a successful compilation.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Class description
3+
*/
4+
class Foo{
5+
String foo
6+
7+
/* Method */
8+
def callBar() {
9+
new Bar().bar(foo)
10+
}
11+
12+
/** Inner class */
13+
class Bar {
14+
15+
def bar(foo) {
16+
println "${foo}Bar"
17+
}
18+
}
19+
}
20+
21+
def foo = new Foo(foo: 'Foo')
22+
foo.callBar()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The file name extension 'exception' not supported for GrEclipse configuration files.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#Whether to use 'space', 'tab' or 'mixed' (both) characters for indentation.
2+
#The default value is 'tab'.
3+
org.eclipse.jdt.core.formatter.tabulation.char=space
4+
5+
#Number of spaces used for indentation in case 'space' characters
6+
#have been selected. The default value is 4.
7+
org.eclipse.jdt.core.formatter.tabulation.size=2
8+
9+
#Number of spaces used for indentation in case 'mixed' characters
10+
#have been selected. The default value is 4.
11+
org.eclipse.jdt.core.formatter.indentation.size=2
12+
13+
#Whether or not indentation characters are inserted into empty lines.
14+
#The default value is 'true'.
15+
org.eclipse.jdt.core.formatter.indent_empty_lines=false
16+
17+
#Number of spaces used for multiline indentation.
18+
#The default value is 2.
19+
groovy.formatter.multiline.indentation=1
20+
21+
#Length after which list are considered too long. These will be wrapped.
22+
#The default value is 30.
23+
groovy.formatter.longListLength=30
24+
25+
#Whether opening braces position shall be the next line.
26+
#The default value is 'false'.
27+
groovy.formatter.braces.start=true
28+
29+
#Whether closing braces position shall be the next line.
30+
#The default value is 'true'.
31+
groovy.formatter.braces.end=true
32+
33+
#Remove unnecessary semicolons. The default value is 'false'.
34+
groovy.formatter.remove.unnecessary.semicolons=true
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Class description
3+
*/
4+
class Foo{
5+
String foo
6+
7+
/* Method */
8+
def callBar() {
9+
new Bar().bar(foo)
10+
}
11+
12+
/** Inner class */
13+
class Bar {
14+
15+
def bar(foo) {
16+
println "${foo}Bar"
17+
}}}
18+
19+
def foo = new Foo(foo: 'Foo');
20+
foo.callBar()

lib/src/main/java/com/diffplug/spotless/SerializableFileFilter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020

2121
/** A file filter with full support for serialization. */
2222
public interface SerializableFileFilter extends FileFilter, Serializable, NoLambda {
23-
/** Creates a FileFilter which will accept all files except files with the given name. */
24-
public static SerializableFileFilter skipFilesNamed(String name) {
25-
return new SerializableFileFilterImpl.SkipFilesNamed(name);
23+
/** Creates a FileFilter which will accept all files except files with the given name(s). */
24+
public static SerializableFileFilter skipFilesNamed(String... names) {
25+
return new SerializableFileFilterImpl.SkipFilesNamed(names);
2626
}
2727
}

lib/src/main/java/com/diffplug/spotless/SerializableFileFilterImpl.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,26 @@
1616
package com.diffplug.spotless;
1717

1818
import java.io.File;
19+
import java.util.Collections;
20+
import java.util.HashSet;
1921
import java.util.Objects;
22+
import java.util.Set;
2023

2124
class SerializableFileFilterImpl {
2225
static class SkipFilesNamed extends NoLambda.EqualityBasedOnSerialization implements SerializableFileFilter {
2326
private static final long serialVersionUID = 1L;
2427

25-
private final String nameToSkip;
28+
private final Set<String> namesToSkip;
2629

27-
SkipFilesNamed(String nameToSkip) {
28-
this.nameToSkip = Objects.requireNonNull(nameToSkip);
30+
SkipFilesNamed(String... namesToSkip) {
31+
Objects.requireNonNull(namesToSkip);
32+
this.namesToSkip = new HashSet<String>(namesToSkip.length);
33+
Collections.addAll(this.namesToSkip, namesToSkip);
2934
}
3035

3136
@Override
3237
public boolean accept(File pathname) {
33-
return !pathname.getName().equals(nameToSkip);
38+
return !namesToSkip.contains(pathname.getName());
3439
}
3540
}
3641
}

0 commit comments

Comments
 (0)